#include "ct_config.h"
#include "station_protocol.h"
#include "ct_uartV2.h"
#include "ct_gpio.h"
#include "ct_sys.h"

// Last sent command and data for resending
StationCommand_t lastCommand;
uint8_t          lastChecksum;
// uint8_t          lastDataLength;
// uint8_t          lastData[STATION_DATA_MAX_LENGTH];

uint8_t         retryCount      = 0;
ModuleType_t    g_ModeuleConfig = MODULE_TYPE_8_STATION;
StationPacket_t Packet;
// Host-assigned station number to hardware station number mapping
uint8_t StationNumMap[MAX_STATION_NUMBER];

#define RECEIVE_COMMAND_ERROR 0x01
uint8_t g_errorCmd;

// GPIO mapping for stations
const ct_gpio_t Station_4_Talbe[MAX_STATION_NUMBER] = {
    { CT_GPIO_PORT1, CT_GPIO_PIN_4 },    // Station 1
    { CT_GPIO_PORT1, CT_GPIO_PIN_3 },    // Station 2
    { CT_GPIO_PORT1, CT_GPIO_PIN_2 },    // Station 3
    { CT_GPIO_PORT1, CT_GPIO_PIN_1 },    // Station 4
};

const ct_gpio_t Station_8_Table[MAX_STATION_NUMBER] = {
    { CT_GPIO_PORT1, CT_GPIO_PIN_0 },    // Station 1
    { CT_GPIO_PORT0, CT_GPIO_PIN_0 },    // Station 2
    { CT_GPIO_PORT0, CT_GPIO_PIN_1 },    // Station 3
    { CT_GPIO_PORT0, CT_GPIO_PIN_3 },    // Station 4
    { CT_GPIO_PORT1, CT_GPIO_PIN_4 },    // Station 5
    { CT_GPIO_PORT1, CT_GPIO_PIN_3 },    // Station 6
    { CT_GPIO_PORT1, CT_GPIO_PIN_2 },    // Station 7
    { CT_GPIO_PORT1, CT_GPIO_PIN_1 },    // Station 8
};
#if 0
const ct_gpio_t StationGPIO[MAX_STATION_NUMBER] = {
    { CT_GPIO_PORT1, CT_GPIO_PIN_4 },    // Station 1
    { CT_GPIO_PORT1, CT_GPIO_PIN_3 },    // Station 2
    { CT_GPIO_PORT1, CT_GPIO_PIN_2 },    // Station 3
    { CT_GPIO_PORT1, CT_GPIO_PIN_1 },    // Station 4
    { CT_GPIO_PORT1, CT_GPIO_PIN_0 },    // Station 5
    { CT_GPIO_PORT0, CT_GPIO_PIN_0 },    // Station 6
    { CT_GPIO_PORT0, CT_GPIO_PIN_1 },    // Station 7
    { CT_GPIO_PORT0, CT_GPIO_PIN_3 },    // Station 8
};
#endif
void CloseAllStationsIo(void) {
    ct_gpio_WritePin(CT_GPIO_PORT0, CT_GPIO_PIN_0 | CT_GPIO_PIN_1 | CT_GPIO_PIN_3,
                     CT_GPIO_LOW);
    ct_gpio_WritePin(CT_GPIO_PORT1, CT_GPIO_PIN_0 | CT_GPIO_PIN_1 | CT_GPIO_PIN_2 | CT_GPIO_PIN_4,
                     CT_GPIO_LOW);
}

static uint8_t GetMaxStationNumber() {
    return (g_ModeuleConfig == MODULE_TYPE_4_STATION) ? MAX_4_STATION_NUMBER : MAX_8_STATION_NUMBER;
}
/**
 * @brief Find the hardware station number corresponding to the host station number.
 * @param hostStationNo The station number assigned by the host.
 * @return The hardware station number or 0xFF if not found.
 */
uint8_t FindHardwareStationNo(uint8_t hostStationNo) {
    uint8_t i;
    uint8_t number = GetMaxStationNumber();
    for (i = 0; i < number; ++i) {
        if (StationNumMap[i] == hostStationNo) {
            return i;    // Return the index as the hardware station number
        }
    }
    return 0xFF;    // Return 0xFF if the station number is not found in the map
}

/**
 * @brief Controls the IO port based on the hardware station number.
 * @param hostStationNo The station number assigned by the host.
 * @param state The state to set the IO port to.
 */
void StationIOCtrl(uint8_t hostStationNo, bool state) {
    ct_gpio_t     *mapping;
    static uint8_t hardwareStationNo;
    hardwareStationNo = FindHardwareStationNo(hostStationNo);
    if (g_ModeuleConfig == MODULE_TYPE_8_STATION) {
        mapping = &Station_8_Table[hardwareStationNo];
    } else {
        mapping = &Station_4_Talbe[hardwareStationNo];
    }
    ct_gpio_WritePin(mapping->port, mapping->pin, state);
}

static void InitStationNumMap(void) {
    uint8_t i;
    uint8_t number = GetMaxStationNumber();
    for (i = 0; i < number; ++i) {
        StationNumMap[i] = 0xFF;    // Initialize to 0xFF to indicate unassigned
    }
}
void station_ModuleTypeCheck(void) {
    ct_gpio_sta_t pin;
    int16_t       i;
    int16_t       lowCount  = 0;
    int16_t       highCount = 0;

    for (i = 0; i < 5; i++) {
        pin = ct_gpio_ReadPin(CT_GPIO_PORT0, CT_GPIO_PIN_4);

        if (pin == CT_GPIO_LOW) {
            lowCount++;
        } else {
            highCount++;
        }
    }

    if (lowCount > highCount) {
        g_ModeuleConfig = MODULE_TYPE_8_STATION;
    } else {
        g_ModeuleConfig = MODULE_TYPE_4_STATION;
    }
}

/**
 * @brief Initializes the station module.
 */
void Station_Init(void) {
    station_ModuleTypeCheck();
    InitStationNumMap();
}

/**********************************************************************/
// CRC-8/Maxim algorithm parameters
#define CRC8_INIT       0xff    // initial value
#define CRC8_POLYNOMIAL 0x31    // this is CRC-8/Maxim algorithm
/**
 * @brief Generates a CRC-8 checksum.
 * @param dat Pointer to the data buffer.
 * @param count Number of bytes in the data buffer.
 * @return The calculated CRC-8 checksum.
 */
uint8_t sensirion_common_generate_crc(uint8_t *dat, uint16_t count) {
    uint16_t current_byte;
    uint8_t  crc = CRC8_INIT;
    uint8_t  crc_bit;
    // calculates 8-Bit checksum with given polynomial
    for (current_byte = 0; current_byte < count; ++current_byte) {
        crc ^= (dat[current_byte]);
        for (crc_bit = 8; crc_bit > 0; --crc_bit) {
            if (crc & 0x80)
                crc = (crc << 1) ^ CRC8_POLYNOMIAL;
            else
                crc = (crc << 1);
        }
    }
    return crc;
}

/**
 * @brief Calculates the CRC for the given data.
 * @param dat Pointer to the data buffer.
 * @param length Number of bytes in the data buffer.
 * @return The calculated CRC.
 */
static uint8_t CalculateCRC(uint8_t *dat, uint16_t length) {
    return sensirion_common_generate_crc(dat, length);
}
/**********************************************************************/
/**
 * @brief Writes a frame to the UART.
 * @param UARTx Pointer to the UART instance.
 * @param command The command to send.
 * @param dat Pointer to the data buffer.
 * @param Length The length of the data.
 * @return The checksum of the sent frame.
 */
static uint8_t Station_WriteFrame(UART_t          *UARTx,
                                  StationCommand_t command,
                                  uint8_t         *dat,
                                  uint8_t          Length) {
    uint8_t i;
    uint8_t checksum = 0;
    uint8_t tx_buff[STATION_FRAME_MAX_LENGTH];
    uint8_t tx_length;
    uint8_t Data;
    if (UARTx == NULL || (Length > STATION_FRAME_MAX_LENGTH)) {
        return 0;    // Invalid parameters
    }

    if (!UARTx->tx_bBusy) {
        tx_buff[0] = STATION_PROTOCOL_HEADER;
        tx_buff[1] = Length + STATION_PROTOCOL_HEADER_LENGTH;
        tx_buff[2] = UARTx->counter++;
        tx_buff[3] = command;

        for (i = 0; i < Length; i++) {
            tx_buff[4 + i] = dat[i];
        }

        checksum = CalculateCRC(tx_buff, Length + 4);
        //
        tx_buff[Length + 4] = checksum;

        tx_length = Length + 5;
        // Start sending the first byte to trigger the interrupt-driven transmission
        ct_uart_PutBuff(UARTx, tx_buff, tx_length);
        if (ringbuf_get(&UARTx->tx_ringbuffer, &Data)) {

            SBUF = Data;    // Start transmission
        }
        // ct_uart_Transmission(UARTx);
        UARTx->tx_bBusy = 1;
    }

    return checksum;
}

/**
 * @brief Reads a frame from the UART using a ring buffer.
 * @param UARTx Pointer to the UART instance.
 * @param Packet Pointer to the station packet to fill.
 * @return The read error code.
 */
static uint8_t Station_ReadFrame(UART_t *UARTx, StationPacket_t *Packet) {
    static STATION_UART_STATE state = WAIT_FOR_HEADER;
    static uint8_t            rx_buff[STATION_FRAME_MAX_LENGTH];    // Temporary storage for the frame
    static uint8_t            index = 0;                            // Index to fill the rx_buff
    uint8_t                   receivedChecksum, i;
    uint8_t                   checksum       = 0;
    uint8_t                   dataByte       = 0;
    static uint8_t            rx_length      = 0;    // Length of the received frame
    int8_t                    retValue       = STATION_READ_NO_ERROR;
    static uint16_t           timeoutCounter = 0;    // Counter to track timeout condition
    RingBuffer_t             *temp_rx_ringbuffer;
    temp_rx_ringbuffer = &UARTx->rx_ringbuffer;
    if (UARTx == NULL || Packet == NULL || temp_rx_ringbuffer == NULL) {
        return STATION_READ_ERROR_OTHER;    // Invalid parameters
    }
    // Check for overflow error
    if (UARTx->rx_bOverflow) {
        UARTx->rx_bOverflow = false;
        state               = WAIT_FOR_HEADER;
        index               = 0;
        timeoutCounter      = 0;
        // Only reset the ring buffer in case of overflow
        ringbuf_init(temp_rx_ringbuffer, temp_rx_ringbuffer->buffer, ringbuf_size(temp_rx_ringbuffer));
        return STATION_READ_ERROR_OVERFLOW;    // Buffer overflow
    }

    // Process the received data based on the current state
    if (ringbuf_elements(temp_rx_ringbuffer) > 0) {
        switch (state) {
            case WAIT_FOR_HEADER:
                if (ringbuf_get(temp_rx_ringbuffer, &dataByte) != -1 && dataByte == STATION_PROTOCOL_HEADER) {
                    rx_buff[index++] = dataByte;    // Store the header
                    state            = WAIT_FOR_LENGTH;
                } else {
                    // Handle header error
                    // HandleHeaderError(UARTx);
                    return STATION_READ_ERROR_HEADER;    // Frame header error
                }

            case WAIT_FOR_LENGTH:
                if (ringbuf_get(temp_rx_ringbuffer, &dataByte) != -1 && dataByte <= STATION_FRAME_MAX_LENGTH) {
                    rx_buff[index++] = dataByte;    // Store the length
                    rx_length        = dataByte;    // +5 for header, length, command, data and checksum
                    state            = WAIT_FOR_DATA;
                } else {
                    // Reset to the initial state if the length is incorrect
                    state = WAIT_FOR_HEADER;
                    index = 0;                           // Reset index for rx_buff
                    return STATION_READ_ERROR_LENGTH;    // Length field error
                }

            case WAIT_FOR_DATA:
                // Wait until the full frame is received
                if (ringbuf_elements(temp_rx_ringbuffer) >= (rx_length - 2)) {    // -2 because header and length are already read
                    // Read the remaining frame data
                    while (index < rx_length) {
                        if (ringbuf_get(temp_rx_ringbuffer, &dataByte) != -1) {
                            rx_buff[index++] = dataByte;    // Store the data
                        } else {
                            // Data is not yet available
                            return STATION_READ_ERROR_INCOMPLETE;
                        }
                    }

                    // Calculate checksum over the frame excluding the checksum byte itself
                    checksum = CalculateCRC(rx_buff, rx_length - 1);

                    // Extract the received checksum from the frame
                    receivedChecksum = rx_buff[rx_length - 1];

                    if (checksum == receivedChecksum) {
                        // Fill the Packet structure with the received data
                        Packet->SerialNum  = rx_buff[2];
                        Packet->cmd        = rx_buff[3];
                        Packet->DataLength = rx_length;    // -5 for header, length, command, and checksum
                        Packet->CheckSum   = checksum;
                        for (i = 0; i < Packet->DataLength - STATION_PROTOCOL_HEADER_LENGTH; i++) {
                            Packet->DataBuff[i] = rx_buff[4 + i];
                        }
                        Packet->rx_cmd      = rx_buff[4];    // Command is the fourth byte in the frame
                        Packet->rx_checksum = rx_buff[5];

                        retValue = STATION_READ_SUCCESS;    // Successfully read packet
                    } else {
                        retValue = STATION_READ_ERROR_CHECKSUM;    // Checksum error
                    }

                    // Reset the state for the next packet
                    state          = WAIT_FOR_HEADER;
                    index          = 0;    // Reset index for rx_buff
                    timeoutCounter = 0;    // Reset timeout counter
                } else {
                    // Increment timeout counter and check for timeout condition
                    timeoutCounter++;
                    if ((timeoutCounter >= 200) || (UARTx->rx_bTimeout == 1)) {
                        // Timeout occurred, reset state and index
                        state              = WAIT_FOR_HEADER;
                        index              = 0;
                        timeoutCounter     = 0;
                        UARTx->rx_bTimeout = 0;
                        return STATION_READ_ERROR_TIMEOUT;    // Timeout error
                    }
                }
                break;
            default:
                state          = WAIT_FOR_HEADER;
                index          = 0;    // Reset index for rx_buff
                timeoutCounter = 0;    // Reset timeout counter
                break;
        }
    }
    return retValue;
}

/*******************************************************************************/
/*******************************************************************************/
/**
 * @brief Opens the station corresponding to the station number received in the packet.
 * @param Packet Pointer to the station packet containing the station number.
 */
void OpenStation(StationPacket_t *Packet) {
    uint8_t hostStationNo = Packet->DataBuff[0];
    uint8_t number        = GetMaxStationNumber();

    if (hostStationNo == 0) {
        g_errorCmd = RECEIVE_COMMAND_ERROR;    // Set error command when station number is 0
        return;
    }
    StationIOCtrl(hostStationNo, true);    // Open the corresponding station
}

/**
 * @brief Closes the station corresponding to the station number received in the packet.
 * @param Packet Pointer to the station packet containing the station number.
 */
void CloseStation(StationPacket_t *Packet) {
    uint8_t hostStationNo = Packet->DataBuff[0];
    uint8_t number        = GetMaxStationNumber();

    if (hostStationNo == 0) {
        CloseAllStationsIo();    // Close all stations if station number is 0
    } else {
        StationIOCtrl(hostStationNo, false);    // Close the corresponding station
    }
}

/**
 * @brief Allocates station numbers based on the list of stations received in the packet.
 * @param Packet Pointer to the station packet containing the list of station numbers.
 */
void AllocateStationNumber(StationPacket_t *Packet) {
    uint8_t hostStationNo, i;
    uint8_t number = GetMaxStationNumber();
    if (Packet->DataLength != number + STATION_PROTOCOL_HEADER_LENGTH) {
        g_errorCmd = RECEIVE_COMMAND_ERROR;    // Set error command when base number is 0 or data length mismatch
        return;
    }
    for (i = 0; i < number; ++i) {
        hostStationNo    = Packet->DataBuff[i];
        StationNumMap[i] = hostStationNo;    // Map to the hardware station numbers
    }
}

/**
 * @brief Uploads the module type to the host.
 * @param Packet Pointer to the station packet containing the module type.
 */
void UploadModuleType(void) {
    // Assuming the module type is in the first byte of the data content.
    uint8_t moduleType = g_ModeuleConfig;
    // Send the module type to the host.
    Station_WriteFrame(&g_Uart0, STATION_CMD_UPLOAD_TYPE, &moduleType, 1);
}

/**
 * @brief Handles an unknown command received in a packet.
 * @param Packet Pointer to the station packet containing the unknown command.
 */
void HandleUnknownCommand(StationPacket_t *Packet) {
    uint8_t unknownCommand = Packet->cmd;
    g_errorCmd             = RECEIVE_COMMAND_ERROR;
    // Handle the unknown command as needed.
    // For example, log the error, send an error response, etc.
}

/**
 * @brief Executes the action based on the received command in the packet.
 * @param Packet Pointer to the station packet containing the command and data.
 */
static void ExecuteCommandAction(StationPacket_t *Packet) {
    // Perform specific actions based on the received command.
    switch (Packet->cmd) {
        case STATION_CMD_OPEN_STATION:
            // Open the corresponding station.
            OpenStation(Packet);
            break;
        case STATION_CMD_CLOSE_STATION:
            // Close the corresponding station.
            CloseStation(Packet);
            break;
        case STATION_CMD_UPLOAD_TYPE:
            // Upload module type.
            UploadModuleType();
            break;
        case STATION_CMD_ALLOCATE_STATION_NO:
            // Allocate station number.
            AllocateStationNumber(Packet);
            break;
        // ... Handle other commands ...
        default:
            // Handle unknown commands.
            HandleUnknownCommand(Packet);
            break;
    }
}

// Timeout handling function.
static int8_t HostResponseTimeout(UART_t *UARTx) {
    int8_t retValue = 0;
    // Check for timeout.
    // if (UARTx->rx_index > 0) {
    if (UARTx->rx_timeout < STATION_RESPONSE_TIMEOUT) {
        UARTx->rx_timeout++;
    } else {
        UARTx->rx_timeout  = 0;
        UARTx->rx_bTimeout = 1;    // Set timeout flag.
        retValue           = 1;
    }
    // }
    return retValue;
}

// Function to be called during module startup.
static void Module_Startup(UART_t *UARTx, ModuleType_t *module_type) {
    lastCommand  = STATION_CMD_UPLOAD_TYPE;
    lastChecksum = Station_WriteFrame(UARTx, STATION_CMD_UPLOAD_TYPE, module_type, 1);
}

// Respond to the host after receiving a command from the slave.
static void Slave_RespondToHost(UART_t *UARTx, uint8_t received_command, uint8_t received_checksum) {
    uint8_t response_data[2];
    response_data[0] = received_command;
    response_data[1] = received_checksum;
    Station_WriteFrame(UARTx, STATION_CMD_SLAVE_RESPONSE, response_data, 2);
}

// Respond to the slave after receiving data from the slave.
static void Host_RespondToSlave(UART_t *UARTx, uint8_t received_command, uint8_t received_checksum) {
    uint8_t response_data[2];
    response_data[0] = received_command;
    response_data[1] = received_checksum;
    Station_WriteFrame(UARTx, STATION_CMD_HOST_RESPONSE, response_data, 2);
}

// Check if the response from the host is valid.
static int8_t IsValidResponse(StationPacket_t *Packet) {
    int8_t retValue = 0;
    // Check if the command from the host is correct.
    if (lastCommand == Packet->rx_cmd) {
        // Check if the checksum from the host is correct.
        if (lastChecksum == Packet->rx_checksum) {
            retValue = 1;
        }
    }
    return retValue;
}

// State machine function.
void Station_StateMachine(UART_t *UARTx, StationPacket_t *Packet) {
    static StationState State = STATION_POWER_ON;
    int8_t              packet_read;

    // State machine cases
    switch (State) {
        case STATION_POWER_ON:
            // Power on state
            Station_Init();

            retryCount        = 0;
            UARTx->rx_timeout = 0;
            State             = INITIALIZE_MODULE;
            break;

        case INITIALIZE_MODULE:
            // Initialize module and send access command

            Module_Startup(UARTx, &g_ModeuleConfig);
            retryCount++;
            State = WAIT_FOR_HOST_RESPONSE;
            break;

        case WAIT_FOR_HOST_RESPONSE:
            // Wait for host response state
            packet_read = Station_ReadFrame(UARTx, Packet);
            if (packet_read == STATION_READ_SUCCESS) {
                retryCount        = 0;
                UARTx->rx_timeout = 0;
                State             = CHECK_RESPONSE;
            } else {
                // If response timeout, resend packet
                if (HostResponseTimeout(UARTx)) {

                    State = RESEND_PACKET;
                }
            }
            break;

        case CHECK_RESPONSE:
            // Check response state
            if (IsValidResponse(Packet)) {
                State = WAIT_FOR_HOST_COMMAND;    // If response is valid, wait for host command
            } else {
                State = WAIT_FOR_HOST_RESPONSE;
            }
            break;
        case WAIT_FOR_HOST_COMMAND:
            // Wait for host command state
            packet_read = Station_ReadFrame(UARTx, Packet);
            if (packet_read == STATION_READ_SUCCESS) {
                State = EXECUTE_ACTION;
            }

            break;

        case EXECUTE_ACTION:
            // Execute action state
            ExecuteCommandAction(Packet);
            State = SLAVE_RESPONSE;
            break;

        case SLAVE_RESPONSE:
            // Slave response state
            if (g_errorCmd != RECEIVE_COMMAND_ERROR) {
                Slave_RespondToHost(UARTx, Packet->cmd, Packet->CheckSum);
            } else {
                g_errorCmd = 0;    // Reset the error command after handling
            }
            State = WAIT_FOR_HOST_COMMAND;
            break;
        case RESEND_PACKET:
            // Resend packet state
            if (retryCount < STATION_MAX_RETRIES) {
                Module_Startup(UARTx, &g_ModeuleConfig);
                retryCount++;
                State = WAIT_FOR_HOST_RESPONSE;    // Transition to wait for host response state
            } else {
                State = RESTART_MODULE;
            }
            break;

        case RESTART_MODULE:
            // Restart module state
            ct_sys_SoftReset();    // Restart the system
            break;

        case ERROR_HANDLING:
            // Handle error
            // HandleError(UARTx);
            State = WAIT_FOR_HOST_COMMAND;
            break;

        default:
            State = STATION_END;
            break;
    }
}

// Main loop function for station state machine
void Station_MainLoop(void) {
    Station_StateMachine(&g_Uart0, &Packet);
}